3 位追蹤者

資料提供器

分頁排序章節中,我們已經描述了如何讓終端使用者選擇要顯示的特定資料頁面,並依某些欄位排序。由於分頁和排序資料的任務非常常見,Yii 提供了一組資料提供器類別來封裝它。

資料提供器是一個實作 yii\data\DataProviderInterface 的類別。它主要支援檢索分頁和排序的資料。它通常用於與資料小工具一起使用,以便終端使用者可以互動式地分頁和排序資料。

Yii 發行版本中包含以下資料提供器類別

所有這些資料提供器的用法都遵循以下常見模式

// create the data provider by configuring its pagination and sort properties
$provider = new XyzDataProvider([
    'pagination' => [...],
    'sort' => [...],
]);

// retrieves paginated and sorted data
$models = $provider->getModels();

// get the number of data items in the current page
$count = $provider->getCount();

// get the total number of data items across all pages
$totalCount = $provider->getTotalCount();

您可以透過配置資料提供器的 paginationsort 屬性來指定資料提供器的分頁和排序行為,這些屬性分別對應於 yii\data\Paginationyii\data\Sort 的配置。您也可以將它們配置為 false 以停用分頁和/或排序功能。

資料小工具,例如 yii\grid\GridView,有一個名為 dataProvider 的屬性,它可以接受資料提供器實例並顯示它提供的資料。例如,

echo yii\grid\GridView::widget([
    'dataProvider' => $dataProvider,
]);

這些資料提供器主要在指定資料來源的方式上有所不同。在以下小節中,我們將解釋每個資料提供器的詳細用法。

Active Data Provider

若要使用 yii\data\ActiveDataProvider,您應該配置其 query 屬性。它可以接受 yii\db\Queryyii\db\ActiveQuery 物件。如果前者,則傳回的資料將為陣列;如果後者,則傳回的資料可以是陣列或 Active Record 實例。例如,

use yii\data\ActiveDataProvider;

$query = Post::find()->where(['status' => 1]);

$provider = new ActiveDataProvider([
    'query' => $query,
    'pagination' => [
        'pageSize' => 10,
    ],
    'sort' => [
        'defaultOrder' => [
            'created_at' => SORT_DESC,
            'title' => SORT_ASC, 
        ]
    ],
]);

// returns an array of Post objects
$posts = $provider->getModels();

如果以上範例中的 $query 是使用以下程式碼建立的,則資料提供器將傳回原始陣列。

use yii\db\Query;

$query = (new Query())->from('post')->where(['status' => 1]); 

注意:如果查詢已指定 orderBy 子句,則終端使用者(透過 sort 配置)給出的新排序指令將附加到現有的 orderBy 子句。任何現有的 limitoffset 子句都將被終端使用者(透過 pagination 配置)的分頁請求覆蓋。

預設情況下,yii\data\ActiveDataProvider 使用 db 應用程式組件作為資料庫連線。您可以透過配置 yii\data\ActiveDataProvider::$db 屬性來使用不同的資料庫連線。

SQL Data Provider

yii\data\SqlDataProvider 使用原始 SQL 語句,該語句用於提取所需的資料。根據 sortpagination 的規範,提供器將相應地調整 SQL 語句的 ORDER BYLIMIT 子句,以僅提取所需順序的請求資料頁面。

若要使用 yii\data\SqlDataProvider,您應該指定 sql 屬性以及 totalCount 屬性。例如,

use yii\data\SqlDataProvider;

$count = Yii::$app->db->createCommand('
    SELECT COUNT(*) FROM post WHERE status=:status
', [':status' => 1])->queryScalar();

$provider = new SqlDataProvider([
    'sql' => 'SELECT * FROM post WHERE status=:status',
    'params' => [':status' => 1],
    'totalCount' => $count,
    'pagination' => [
        'pageSize' => 10,
    ],
    'sort' => [
        'attributes' => [
            'title',
            'view_count',
            'created_at',
        ],
    ],
]);

// returns an array of data rows
$models = $provider->getModels();

資訊:只有在您需要分頁資料時才需要 totalCount 屬性。這是因為透過 sql 指定的 SQL 語句將被提供器修改,以僅傳回當前請求的資料頁面。提供器仍然需要知道資料項目的總數,以便正確計算可用的頁面數。

Array Data Provider

當使用大型陣列時,最好使用 yii\data\ArrayDataProvider。提供器允許您傳回按一個或多個欄位排序的陣列資料頁面。若要使用 yii\data\ArrayDataProvider,您應該將 allModels 屬性指定為大型陣列。大型陣列中的元素可以是關聯陣列(例如 DAO 的查詢結果)或物件(例如 Active Record 實例)。例如,

use yii\data\ArrayDataProvider;

$data = [
    ['id' => 1, 'name' => 'name 1', ...],
    ['id' => 2, 'name' => 'name 2', ...],
    ...
    ['id' => 100, 'name' => 'name 100', ...],
];

$provider = new ArrayDataProvider([
    'allModels' => $data,
    'pagination' => [
        'pageSize' => 10,
    ],
    'sort' => [
        'attributes' => ['id', 'name'],
    ],
]);

// get the rows in the currently requested page
$rows = $provider->getModels();

注意:Active Data ProviderSQL Data Provider 相比,陣列資料提供器的效率較低,因為它需要將所有資料載入記憶體中。

使用資料鍵

當使用資料提供器傳回的資料項目時,您通常需要使用唯一鍵來識別每個資料項目。例如,如果資料項目代表客戶資訊,您可能希望使用客戶 ID 作為每個客戶資料的鍵。資料提供器可以傳回與 yii\data\DataProviderInterface::getModels() 傳回的資料項目相對應的此類鍵的清單。例如,

use yii\data\ActiveDataProvider;

$query = Post::find()->where(['status' => 1]);

$provider = new ActiveDataProvider([
    'query' => $query,
]);

// returns an array of Post objects
$posts = $provider->getModels();

// returns the primary key values corresponding to $posts
$ids = $provider->getKeys();

在以上範例中,由於您向 yii\data\ActiveDataProvider 提供了 yii\db\ActiveQuery 物件,因此它足夠智慧,可以傳回主鍵值作為鍵。您也可以透過使用欄位名稱或計算鍵值的可呼叫物件配置 yii\data\ActiveDataProvider::$key 來明確指定應如何計算鍵值。例如,

// use "slug" column as key values
$provider = new ActiveDataProvider([
    'query' => Post::find(),
    'key' => 'slug',
]);

// use the result of md5(id) as key values
$provider = new ActiveDataProvider([
    'query' => Post::find(),
    'key' => function ($model) {
        return md5($model->id);
    }
]);

建立自訂資料提供器

若要建立您自己的自訂資料提供器類別,您應該實作 yii\data\DataProviderInterface。更簡單的方法是從 yii\data\BaseDataProvider 擴充,這讓您可以專注於核心資料提供器邏輯。特別是,您主要需要實作以下方法

  • prepareModels():準備將在目前頁面中提供的資料模型,並將它們作為陣列傳回。
  • prepareKeys():接受目前可用資料模型的陣列,並傳回與它們關聯的鍵。
  • prepareTotalCount:傳回指示資料提供器中資料模型總數的值。

以下是有效讀取 CSV 資料的資料提供器範例

<?php
use yii\data\BaseDataProvider;

class CsvDataProvider extends BaseDataProvider
{
    /**
     * @var string name of the CSV file to read
     */
    public $filename;
    
    /**
     * @var string|callable name of the key column or a callable returning it
     */
    public $key;
    
    /**
     * @var SplFileObject
     */
    protected $fileObject; // SplFileObject is very convenient for seeking to particular line in a file
    
 
    /**
     * {@inheritdoc}
     */
    public function init()
    {
        parent::init();
        
        // open file
        $this->fileObject = new SplFileObject($this->filename);
    }
 
    /**
     * {@inheritdoc}
     */
    protected function prepareModels()
    {
        $models = [];
        $pagination = $this->getPagination();
 
        if ($pagination === false) {
            // in case there's no pagination, read all lines
            while (!$this->fileObject->eof()) {
                $models[] = $this->fileObject->fgetcsv();
                $this->fileObject->next();
            }
        } else {
            // in case there's pagination, read only a single page
            $pagination->totalCount = $this->getTotalCount();
            $this->fileObject->seek($pagination->getOffset());
            $limit = $pagination->getLimit();
 
            for ($count = 0; $count < $limit; ++$count) {
                $models[] = $this->fileObject->fgetcsv();
                $this->fileObject->next();
            }
        }
 
        return $models;
    }
 
    /**
     * {@inheritdoc}
     */
    protected function prepareKeys($models)
    {
        if ($this->key !== null) {
            $keys = [];
 
            foreach ($models as $model) {
                if (is_string($this->key)) {
                    $keys[] = $model[$this->key];
                } else {
                    $keys[] = call_user_func($this->key, $model);
                }
            }
 
            return $keys;
        }

        return array_keys($models);
    }
 
    /**
     * {@inheritdoc}
     */
    protected function prepareTotalCount()
    {
        $count = 0;
 
        while (!$this->fileObject->eof()) {
            $this->fileObject->next();
            ++$count;
        }
 
        return $count;
    }
}

使用資料篩選器過濾資料提供器

雖然您可以如過濾資料分離的篩選表單章節中所述,手動為 active data provider 建構條件,但如果您需要彈性的篩選條件,Yii 具有非常有用的資料篩選器。資料篩選器可以如下使用

$filter = new ActiveDataFilter([
    'searchModel' => 'app\models\PostSearch'
]);

$filterCondition = null;

// You may load filters from any source. For example,
// if you prefer JSON in request body,
// use Yii::$app->request->getBodyParams() below:
if ($filter->load(\Yii::$app->request->get())) { 
    $filterCondition = $filter->build();
    if ($filterCondition === false) {
        // Serializer would get errors out of it
        return $filter;
    }
}

$query = Post::find();
if ($filterCondition !== null) {
    $query->andWhere($filterCondition);
}

return new ActiveDataProvider([
    'query' => $query,
]);

PostSearch 模型用於定義允許哪些屬性和值進行篩選

use yii\base\Model;

class PostSearch extends Model 
{
    public $id;
    public $title;
    
    public function rules()
    {
        return [
            ['id', 'integer'],
            ['title', 'string', 'min' => 2, 'max' => 200],            
        ];
    }
}

資料篩選器非常靈活。您可以自訂條件的建構方式以及允許哪些運算符。如需詳細資訊,請查看關於 yii\data\DataFilter 的 API 文件。

發現錯字或您認為此頁面需要改進嗎?
在 github 上編輯 !